﻿Imports System.ComponentModel
Imports HtmlAgilityPack
Imports System.Collections.ObjectModel
Imports TwoLine
Imports System.Text.RegularExpressions
Imports System.Threading.Tasks
Imports System.Threading

Namespace OfflineLibrary_WPF
	Class MainWindow
		Implements INotifyPropertyChanged

		Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

		Private Sub NotifyPropertyChanged(ByVal info As String)
			RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
		End Sub

		Public Shared baseURL As New Uri("http://wol.jw.org/")
		Public WithEvents bgDownloader As New BackgroundWorker
		Public WithEvents bgDownloadAll As New BackgroundWorker
		Private WithEvents tmrUpdateProgress As New Timers.Timer
		Private browserHistory As New Stack(Of String)
		Private currentPage As String

		Private _NavigateToFragment As String
		Private Property NavigateToFragment As String
			Get
				If _NavigateToFragment IsNot Nothing AndAlso _NavigateToFragment.StartsWith("#h=") Then
					Return String.Format("p{0}", Regex.Match(_NavigateToFragment, "(?<==)[\d]+(?=:)"))
				Else
					Return _NavigateToFragment
				End If
			End Get
			Set(value As String)
				_NavigateToFragment = value
			End Set
		End Property

		Private ParallelNavigateToFragment As String

#Region "Progress bar stuff."
		Public Sub UpdateProgressBar()
			NotifyPropertyChanged("ProgressBarMax")
			NotifyPropertyChanged("ProgressBarValue")
			NotifyPropertyChanged("ProgressBarText")
		End Sub
		Public Property DownloadQueue As Concurrent.ConcurrentQueue(Of Uri)
			Get
				UpdateProgressBar()
				'start the timer that will update the progress bar
				tmrUpdateProgress.Enabled = True
				Return Pages.DownloadQueue
			End Get
			Set(value As Concurrent.ConcurrentQueue(Of Uri))
				Pages.DownloadQueue = value
			End Set
		End Property

		Public Property ProgressBarValue As Integer
		'Public ReadOnly Property ProgressBarValue As Integer
		'	Get
		'		Return DownloadProgressBar.Maximum - Pages.DownloadQueue.Count
		'	End Get
		'End Property

		Public Property ProgressBarMax As Integer
		'Public ReadOnly Property ProgressBarMax As Integer
		'	Get
		'		If Pages.DownloadQueue.Count = 0 Then
		'			Return 0
		'		ElseIf Pages.DownloadQueue.Count > DownloadProgressBar.Maximum Then
		'			'more items have been added to the queue
		'			'just increase the size of the bar instead of moving the value, it is nearly as accurate and a whole lot simpler
		'			Return Pages.DownloadQueue.Count
		'		ElseIf Pages.DownloadQueue.Count < DownloadProgressBar.Maximum Then
		'			'if there are less items in the queue, then work is being done and the progress bar should move
		'			NotifyPropertyChanged("ProgressBarValue")
		'			Return DownloadProgressBar.Maximum
		'		End If

		'		Return DownloadProgressBar.Maximum
		'	End Get
		'End Property

		Public ReadOnly Property ProgressBarText As String
			Get
				'keep this collection to the last 100 downloaded for memory management reasons
				PagesDownloaded = New ObservableCollection(Of String)(PagesDownloaded.Skip(PagesDownloaded.Count - 100).Take(100).ToList)
				Return If(PagesDownloaded.Count > 0, PagesDownloaded.Last(), "")
			End Get
		End Property

		Private Sub tmrUpdateProgress_Elapsed() Handles tmrUpdateProgress.Elapsed
			Dim AllRequests As Integer = DownloadQueue.Count + urlsToGet.Count + OutstandingLinkedPages
			Dim CompletedRequests As Integer = RetrievedLinkedPages
			Dim AllOutstadingRequests As Integer = AllRequests - CompletedRequests

			Debug.WriteLine(AllOutstadingRequests)

			If AllRequests > ProgressBarMax Then
				ProgressBarMax = AllRequests
			End If

			If CompletedRequests > ProgressBarValue Then
				ProgressBarValue = CompletedRequests
			End If

			ProgressBarValue += 1
			'however, we are never done if the queue is growing
			If ProgressBarValue > ProgressBarMax Then
				ProgressBarValue = 0
			End If

			If AllOutstadingRequests = 0 Then
				'clear the progress bar
				ProgressBarValue = 0
				ProgressBarMax = 0

				'clear the page counter
				OutstandingLinkedPages = 0
				RetrievedLinkedPages = 0

				'disable the progress bar update
				tmrUpdateProgress.Enabled = False
				PagesDownloaded.Clear()
			End If

			'it may appear that the progress bar doesn't count all the links, but in fact, not all links are to be downloaded, some are skipped and 'download quickly' in that case
			'Select Case AllOutstadingRequests
			'	Case Is <= 0
			'		'all outstanding work is done; reset the progress bar
			'		ProgressBarValue = 0
			'		ProgressBarMax = 0
			'		tmrUpdateProgress.Enabled = False
			'		PagesDownloaded.Clear()
			'	Case Is < ProgressBarMax
			'		ProgressBarValue = ProgressBarMax - AllOutstadingRequests
			'	Case Is = ProgressBarMax
			'		'just pretend work is being done
			'		ProgressBarValue += 1
			'	Case Is > ProgressBarMax
			'		ProgressBarMax = AllOutstadingRequests
			'		'this isn't really true, but it shows that work is being done, which it is
			'		ProgressBarValue += 1
			'		'however, we are never done if the queue is growing
			'		If ProgressBarValue > ProgressBarMax Then
			'			ProgressBarValue = 0
			'		End If
			'End Select

			UpdateProgressBar()
		End Sub
#End Region

		Private Sub btnUpdateLanguages_Click(sender As Object, e As System.EventArgs) Handles btnUpdateLanguages.Click
			UpdateLanguages()
			cmboLanguages.ItemsSource = GetLanguages()
			cmboParallelLanguages.ItemsSource = GetLanguages()
		End Sub

		Private Sub MainWindow_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles Me.Closing
			'bgDownloader.CancelAsync()

			'this will make sure the current background operations to the database are finished
			'but it currently takes too long to execute
			'Do While bgDownloader.IsBusy
			'	System.Threading.Thread.Sleep(100)
			'Loop
		End Sub

		Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
			'check to make sure the database exists first
			Try
				SetOfflineDataDatabase()
			Catch ex As UnauthorizedAccessException
				MessageBox.Show("I can't access the data storage location, please pick a new one and restart the program.", "Data Access Error", MessageBoxButton.OK, MessageBoxImage.Error)
				btnSetDownloadLocation_Click(Nothing, Nothing)
				Me.Close()
			End Try

			cmboLanguages.ItemsSource = GetLanguages()
			cmboParallelLanguages.ItemsSource = GetLanguages()

			Dim browseModeOptions As New List(Of KeyValuePair(Of String, BrowseModes))
			browseModeOptions.Add(New KeyValuePair(Of String, BrowseModes)("Offline", BrowseModes.Offline))
			browseModeOptions.Add(New KeyValuePair(Of String, BrowseModes)("Offline - Get Missing Pages", BrowseModes.OfflineGetMissingPage))
			browseModeOptions.Add(New KeyValuePair(Of String, BrowseModes)("Offline - Get Linked Pages", BrowseModes.OfflineGetLinkedPages))
			browseModeOptions.Add(New KeyValuePair(Of String, BrowseModes)("Online", BrowseModes.Online))
			browseModeOptions.Add(New KeyValuePair(Of String, BrowseModes)("Online - Get Linked Pages", BrowseModes.OnlineGetLinkedPages))

			cmboOfflineMode.ItemsSource = browseModeOptions
			cmboOfflineMode.SelectedValue = BrowseModes.Offline

			Dim romanizationOptions As New List(Of KeyValuePair(Of String, RomanizationType))
			romanizationOptions.Add(New KeyValuePair(Of String, RomanizationType)("No Romanization", RomanizationType.None))
			romanizationOptions.Add(New KeyValuePair(Of String, RomanizationType)("Pīnyīn (Mandarin)", RomanizationType.Pinyin))
			romanizationOptions.Add(New KeyValuePair(Of String, RomanizationType)("Sydney Lau (Cantonese)", RomanizationType.SydneyLau))
			romanizationOptions.Add(New KeyValuePair(Of String, RomanizationType)("Zhù​yīn​ Fú​hào​ (Mandarin)", RomanizationType.Zhuyin))

			cmboRomanization.ItemsSource = romanizationOptions
			cmboParallelRomanization.ItemsSource = romanizationOptions
			cmboRomanization.SelectedValue = RomanizationType.None
			cmboParallelRomanization.SelectedValue = RomanizationType.None


			bgDownloader.WorkerSupportsCancellation = True
			tmrUpdateProgress.AutoReset = True
			tmrUpdateProgress.Interval = 1000

		End Sub

		Private Sub cmboLanguages_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles cmboLanguages.SelectionChanged

			Dim URL As New Uri(baseURL, cmboLanguages.SelectedValue.ToString & "/")

			NavigateTo(URL, True)
		End Sub

		Private Sub mainBrowser_LoadCompleted(sender As Object, e As System.Windows.Navigation.NavigationEventArgs) Handles mainBrowser.LoadCompleted
			If Not String.IsNullOrWhiteSpace(NavigateToFragment) Then
				mainBrowser.InvokeScript("showIt", NavigateToFragment.Replace("#", ""))
				NavigateToFragment = String.Empty
			End If
		End Sub

		Private Sub parallelBrowser_LoadCompleted(sender As Object, e As System.Windows.Navigation.NavigationEventArgs) Handles parallelBrowser.LoadCompleted
			If Not String.IsNullOrWhiteSpace(NavigateToFragment) Then
				Try
					parallelBrowser.InvokeScript("showIt", NavigateToFragment.Replace("#", ""))
					ParallelNavigateToFragment = String.Empty
				Catch ex As Exception
					'there probably is no parallel page loaded
				End Try
			End If
		End Sub

		Private Sub mainBrowser_Navigating(sender As Object, e As System.Windows.Navigation.NavigatingCancelEventArgs) Handles mainBrowser.Navigating
			If e.Uri IsNot Nothing Then
				tmrUpdateProgress.Enabled = True
				NavigateTo(e.Uri, True)
			End If
		End Sub

		Private Sub parallelBrowser_Navigating(sender As Object, e As System.Windows.Navigation.NavigatingCancelEventArgs) Handles parallelBrowser.Navigating
			If e.Uri IsNot Nothing Then
				ParallelNavigateTo(e.Uri)
			End If
		End Sub

		Private Sub cmboOfflineMode_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles cmboOfflineMode.SelectionChanged
			BrowseMode = DirectCast(sender, ComboBox).SelectedValue
		End Sub

		Private Sub bgDownloader_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgDownloader.DoWork
			Dim URL As Uri = Nothing

			Dim availableWorkerThreads As Integer
			Dim availableCompletionPortThreads As Integer

			'we don't want to much load on the local machine or on the server
			ThreadPool.SetMaxThreads(10, 10)

			Do While Not e.Cancel AndAlso DownloadQueue.TryDequeue(URL)
				'GetLinkedPages(URL, LocalFileLocation)
				ThreadPool.GetAvailableThreads(availableWorkerThreads, availableCompletionPortThreads)
				If availableCompletionPortThreads > 0 AndAlso availableWorkerThreads > 0 Then
					ThreadPool.QueueUserWorkItem(AddressOf GetLinkedPagesHelper, URL)
				Else
					Thread.Sleep(100)
				End If
				'DownloadQueue.TryDequeue(URL)
			Loop
		End Sub

		Private Sub GetLinkedPagesHelper(URL As Uri)
			GetLinkedPages(URL, LocalFileLocation)
		End Sub

		Private Sub bgDownloadAll_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgDownloadAll.DoWork
			Dim URL As Uri = DirectCast(e.Argument, Uri)

			GetNestedLinkedPages(GetPage(URL, LocalFileLocation, bgDownloader), LocalFileLocation, bgDownloader, 0)
		End Sub

		Private Function FixNavigationURL(URL As Uri) As Uri
			Dim URLToNavigateTo As Uri

			If URL.Scheme = "about" Then
				URLToNavigateTo = New Uri(baseURL, URL.AbsolutePath)
			Else
				URLToNavigateTo = URL
			End If

			Return URLToNavigateTo
		End Function

		Private Sub NavigateTo(URL As Uri, IncludeInHistory As Boolean)
			Dim URLToNavigateTo As Uri = FixNavigationURL(URL)

			If IncludeInHistory Then
				browserHistory.Push(currentPage)
			End If

			txtAddressBar.Text = URLToNavigateTo.AbsoluteUri & URL.Fragment
			currentPage = txtAddressBar.Text

			Dim htmlPage As HtmlDocument = GetPage(URLToNavigateTo, LocalFileLocation, bgDownloader)
			If htmlPage.DocumentNode.ChildNodes.Count > 0 Then

				Dim textPage As String = htmlPage.DocumentNode.OuterHtml
				'add second line
				Dim secondLineAdder As New TwoLine(If(cmboRomanization.SelectedItem IsNot Nothing, cmboRomanization.SelectedValue, RomanizationType.None))
				textPage = secondLineAdder.AddTwoLine(textPage)

				mainBrowser.NavigateToString(textPage)

				If URL.Fragment.Length > 0 Then
					NavigateToFragment = URL.Fragment
					'it will be the same fragment, but the fragment isn't in the database for retrieval
					ParallelNavigateToFragment = URL.Fragment
				End If
			End If

			ParallelNavigateTo(GetAlternateLanguagePages(URL, If(cmboParallelLanguages.SelectedValue IsNot Nothing, cmboParallelLanguages.SelectedValue.ToString, "")))

			tmrUpdateProgress.Enabled = True

		End Sub

		Private Sub NavigateTo(NavigationTarget As String, IncludeInHistory As Boolean)
			Try
				Dim URL As New Uri(NavigationTarget)
				NavigateTo(URL, IncludeInHistory)
			Catch ex As Exception
				If IncludeInHistory Then
					browserHistory.Push(currentPage)
					currentPage = NavigationTarget
				End If

				mainBrowser.NavigateToString(GetPagesWithSearchTerms(txtAddressBar.Text))
			End Try

			tmrUpdateProgress.Enabled = True

		End Sub

		Private Sub ParallelNavigateTo(URL As Uri)
			Dim URLToNavigateTo As Uri = FixNavigationURL(URL)

			Dim htmlPage As HtmlDocument = GetPage(URLToNavigateTo, LocalFileLocation, bgDownloader)
			If htmlPage.DocumentNode.ChildNodes.Count > 0 Then

				Dim textPage As String = htmlPage.DocumentNode.OuterHtml
				'add second line
				Dim secondLineAdder As New TwoLine(If(cmboParallelRomanization.SelectedItem IsNot Nothing, cmboParallelRomanization.SelectedValue, RomanizationType.None))
				textPage = secondLineAdder.AddTwoLine(textPage)

				parallelBrowser.NavigateToString(textPage)

				If URL.Fragment.Length > 0 Then
					ParallelNavigateToFragment = URL.Fragment
				End If
			End If

			tmrUpdateProgress.Enabled = True

		End Sub

		Public Sub ParallelNavigateTo(Page As String)
			Dim htmlPage As New HtmlDocument
			htmlPage.LoadHtml(Page)

			Dim documentANodes As HtmlNodeCollection = htmlPage.DocumentNode.SelectNodes("//a")

			If documentANodes IsNot Nothing AndAlso documentANodes.Count = 1 Then
				ParallelNavigateTo(New Uri(htmlPage.DocumentNode.SelectSingleNode("//a").Attributes("href").Value))
			Else
				parallelBrowser.NavigateToString(Page)
				'there is no fragment on a search page
				ParallelNavigateToFragment = String.Empty
			End If

			tmrUpdateProgress.Enabled = True

		End Sub

		Private Sub btnBack_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles btnBack.Click
			NavigateTo(browserHistory.Pop, False)
		End Sub

		Private Sub btnDownloadAll_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles btnDownloadAll.Click
			If Not bgDownloadAll.IsBusy Then
				bgDownloadAll.RunWorkerAsync(New Uri(baseURL, cmboLanguages.SelectedValue.ToString & "/"))
			End If

			tmrUpdateProgress.Enabled = True
		End Sub

		Private Sub DownloadProgressBarText_MouseMove(sender As Object, e As System.Windows.Input.MouseEventArgs) Handles DownloadProgressBarText.MouseMove
			UpdateProgressBar()
		End Sub

		Private Sub btnGo_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles btnGo.Click
			NavigateTo(txtAddressBar.Text, True)
		End Sub

		Private Sub btnToggleParallelBrowsing_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles btnToggleParallelBrowsing.Click
			If gridParallelBrowsing.Visibility = Windows.Visibility.Collapsed Then
				gridParallelBrowsing.Visibility = Windows.Visibility.Visible
				colParallelBrowsing.Width = New GridLength(1, GridUnitType.Star)
			Else
				gridParallelBrowsing.Visibility = Windows.Visibility.Collapsed
				colParallelBrowsing.Width = New GridLength(0)
			End If
		End Sub

		Private Sub btnSetDownloadLocation_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles btnSetDownloadLocation.Click
			Dim dialog As New System.Windows.Forms.FolderBrowserDialog

			If dialog.ShowDialog = Forms.DialogResult.OK Then
				LocalFileLocation = New IO.DirectoryInfo(dialog.SelectedPath)
			End If

		End Sub

		Private Sub btnHelp_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles btnHelp.Click
			Dim helpLanguageCode As String = "e"
			Dim helpFile As IO.FileInfo
			Dim helpText As String

			If cmboLanguages.SelectedValue IsNot Nothing Then
				helpLanguageCode = cmboLanguages.SelectedValue.ToString.ToLowerInvariant
			End If

			helpFile = New IO.FileInfo(String.Format("{0}{1}help{1}{2}.html", LocalFileLocation, IO.Path.DirectorySeparatorChar, helpLanguageCode))

			If Not helpFile.Exists Then
				helpFile = New IO.FileInfo(String.Format("{0}{1}help{1}{2}.html", LocalFileLocation, IO.Path.DirectorySeparatorChar, "e"))
			End If

			If helpFile.Exists Then
				Using myReader As IO.StreamReader = IO.File.OpenText(helpFile.FullName)
					helpText = myReader.ReadToEnd
				End Using

				mainBrowser.NavigateToString(helpText)
			Else
				mainBrowser.NavigateToString("Help file not found!")
			End If
		End Sub
	End Class
End Namespace
